package top.wshape1.shiziapi.uaa.service.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import top.wshape1.shiziapi.common.constant.BizCodeEnum;
import top.wshape1.shiziapi.common.constant.RedisConstant;
import top.wshape1.shiziapi.common.domain.vo.PageVo;
import top.wshape1.shiziapi.common.exception.CustomException;
import top.wshape1.shiziapi.common.utils.JwtUtil;
import top.wshape1.shiziapi.common.utils.R;
import top.wshape1.shiziapi.uaa.config.ShiZiApiConfig;
import top.wshape1.shiziapi.uaa.domain.LoginUser;
import top.wshape1.shiziapi.uaa.domain.User;
import top.wshape1.shiziapi.uaa.domain.vo.InfoVo;
import top.wshape1.shiziapi.uaa.domain.vo.MemberVo;
import top.wshape1.shiziapi.uaa.domain.vo.PasswordVo;
import top.wshape1.shiziapi.uaa.domain.vo.UserLoginVo;
import top.wshape1.shiziapi.uaa.feign.ThirdPartyFeignService;
import top.wshape1.shiziapi.uaa.mapper.UserMapper;
import top.wshape1.shiziapi.uaa.service.UserService;
import top.wshape1.shiziapi.uaa.utils.UserUtil;

import java.util.Collection;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author Wshape1
 * @description 针对表【t_user】的数据库操作Service实现
 * @since 2023-08-22 15:01:00
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Resource
    public RedisTemplate<String, Object> redisTemplate;

    @Lazy
    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private ShiZiApiConfig shiZiApiConfig;

    @Resource
    private ThirdPartyFeignService thirdPartyFeignService;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    @Override
    public String login(UserLoginVo userLoginVo) {

        // 使用提供的邮箱和密码创建认证令牌。
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userLoginVo.getEmail(), userLoginVo.getPassword());

        Authentication authenticate;
        try {
            // 使用提供的邮箱和密码创建认证令牌。
            authenticate = authenticationManager.authenticate(authenticationToken);
        } catch (Exception e) {
            // 处理认证异常，通过抛出自定义异常并提供适当的错误消息。
            throw new CustomException(R.instance(BizCodeEnum.EMAIL_PASSWORD_ERROR.code(), BizCodeEnum.EMAIL_PASSWORD_ERROR.msg()));
        }
        // 检查认证是否成功。
        if (!authenticate.isAuthenticated()) {
            // 如果未通过认证，抛出自定义异常并提供适当的错误消息。
            throw new CustomException(R.instance(BizCodeEnum.EMAIL_PASSWORD_ERROR.code(), BizCodeEnum.EMAIL_PASSWORD_ERROR.msg()));
        }
        // 从Principal中提取已认证的User对象，并移除敏感数据，如密码。
        User user = ((LoginUser) authenticate.getPrincipal()).getUser();
        user.setPassword(null);
        // 将用户ID转换为字符串。
        String idStr = user.getId().toString();
        // 生成JWT
        String jwt = JwtUtil.createJWT(idStr, JSON.toJSONString(user), shiZiApiConfig.SIGN_KEY);

        // 存入Redis 并设置过期时间
        redisTemplate.opsForValue().set(RedisConstant.USER_JWT_KEY + idStr, jwt, shiZiApiConfig.DURATION_DAYS, TimeUnit.DAYS);

        return jwt;
    }

    @Override
    public void logout(User user) {
        // 写一段用户注销代码：删除Redis中的数据即可。
        redisTemplate.delete(RedisConstant.USER_JWT_KEY + user.getId());
    }

    @Override
    public void sendRegCode(String email) {
        // 先检查是否在一分钟内已经有发送过邮件
        String key = RedisConstant.EMAIL_REG_CODE_KEY + email;
        checkEmailSend(key);

        // 再检查数据库是否存在该邮箱账号  注册
        if (count(new LambdaQueryWrapper<User>().eq(User::getEmail, email)) != 0) {
            throw new CustomException(R.instance(BizCodeEnum.EMAIL_EXIST_ERROR.code(), BizCodeEnum.EMAIL_EXIST_ERROR.msg()));
        }

        // 发送邮件
        sendCodeMail("reg", email, null);

    }

    @Override
    public void sendForgetUrl(String email) {
        // 先检查是否在一分钟内已经有发送过邮件
        String key = RedisConstant.EMAIL_FORGET_CODE_KEY + email;
        checkEmailSend(key);

        User one = getOne(new LambdaQueryWrapper<User>().eq(User::getEmail, email));
        // 再检查数据库是否存在该邮箱账号  忘记密码
        if (one == null) {
            throw new CustomException(R.instance(BizCodeEnum.EMAIL_NOT_EXIST_ERROR.code(), BizCodeEnum.EMAIL_NOT_EXIST_ERROR.msg()));
        }

        // 发送邮件
        sendCodeMail("forget", email, one.getId());
    }

    @Override
    public void reg(UserLoginVo userLoginVo) {
        String email = userLoginVo.getEmail();
        // 验证码与Redis中的验证码不匹配    // TODO 方便测试
        if (!"-6666".equals(userLoginVo.getCode()) && !userLoginVo.getCode()
                .equals(redisTemplate.opsForValue().get(RedisConstant.EMAIL_REG_CODE_KEY + email)))
            throw new CustomException(R.instance(BizCodeEnum.INVALID_CODE_ERROR.code(), BizCodeEnum.INVALID_CODE_ERROR.msg()));

        redisTemplate.delete(RedisConstant.EMAIL_REG_CODE_KEY + email);

        // 创建用户对象
        User user = new User();
        Date date = new Date();
        user.setStatus(1);
        user.setCreateTime(date);
        user.setUpdateTime(date);
        user.setPassword(passwordEncoder.encode(userLoginVo.getPassword()));
        user.setEmail(email);
        user.setNickname(email.split("@")[0]);
        // 保存用户
        // 保存到数据库后 会返回id给user
        baseMapper.insert(user);

        // 异步请求 更新用户默认头像
        CompletableFuture.runAsync(() -> {
            try {
                R<Object> r = thirdPartyFeignService.generateAvatarAndStoreIfAbsent(user.getId());
                if (r.isSuccessful()) {
                    String avatarUrl = r.getMsg();
                    User update = new User();
                    update.setId(user.getId());
                    update.setAvatarUrl(avatarUrl);
                    baseMapper.updateById(update);
                } else {
                    log.error("注册用户{}, 邮箱{}，出现了远程服务错误：{}", user.getId(), user.getEmail(), r.getData());
                }
            } catch (Exception e) {
                log.error("注册用户{}, 邮箱{}，出现了错误：{}", user.getId(), user.getEmail(), e.getMessage());
            }
        }, threadPoolExecutor);

    }

    @Override
    public User getFromEncodedString(String userEncoded) {
        return UserUtil.getFromEncodedString(userEncoded);
    }

    @Override
    public void resetPassword(UserLoginVo userLoginVo) {
        String email = userLoginVo.getEmail();
        // 验证码与Redis中的验证码不匹配
        if (!userLoginVo.getCode()
                .equals(redisTemplate.opsForValue().get(RedisConstant.EMAIL_FORGET_CODE_KEY + email)))
            throw new CustomException(R.instance(BizCodeEnum.INVALID_CODE_ERROR.code(), BizCodeEnum.INVALID_CODE_ERROR.msg()));


        User update = new User();
        update.setId(userLoginVo.getId());
        update.setPassword(passwordEncoder.encode(userLoginVo.getPassword()));
        update.setUpdateTime(new Date());

        int row = baseMapper.updateById(update);

        redisTemplate.delete(RedisConstant.EMAIL_REG_CODE_KEY + email);
        if (row > 0)
            redisTemplate.delete(RedisConstant.USER_JWT_KEY + update.getId());
    }

    @Override
    public void modifyPassword(User user, PasswordVo passwordVo) {

        // 填写的新旧密码相同
        if (passwordVo.getOldPassword().equals(passwordVo.getNewPassword())) {
            throw new CustomException(R.instance(BizCodeEnum.SAME_PASSWORD_ERROR));
        }

        user = getById(user.getId());

        // 如果没有绑定邮箱的，就是没有密码的
        if (user == null || user.getPassword() == null) {
            throw new CustomException(R.instance(BizCodeEnum.EMAIL_PASSWORD_ERROR));
        }

        // 旧密码和数据库的密码不一致
        if (!passwordEncoder.matches(passwordVo.getOldPassword(), user.getPassword())) {
            throw new CustomException(R.instance(BizCodeEnum.EMAIL_PASSWORD_ERROR));
        }

        // 修改密码
        User update = new User();
        update.setId(user.getId());
        update.setPassword(passwordEncoder.encode(passwordVo.getNewPassword()));
        update.setUpdateTime(new Date());

        baseMapper.updateById(update);

    }

    @Override
    public void sendBindingCode(User user, String email) {
        // 先检查是否在一分钟内已经有发送过邮件
        String key = RedisConstant.EMAIL_BINDING_CODE_KEY + user.getId() + '-' + email;
        checkEmailSend(key);

        // 如果已经绑定了邮箱 或者 该邮箱已经被绑定
        if (user.getEmail() != null || count(new LambdaQueryWrapper<User>().eq(User::getEmail, email)) != 0) {
            throw new CustomException(R.instance(BizCodeEnum.EMAIL_EXIST_ERROR.code(), BizCodeEnum.EMAIL_EXIST_ERROR.msg()));
        }

        // 发送邮件
        sendCodeMail("binding", email, user.getId());
    }

    @Override
    public void bindingEmail(User user, String email, String password, String code) {

        String key = RedisConstant.EMAIL_BINDING_CODE_KEY + user.getId() + '-' + email;

        // 验证码不通过
        if (!code.equals(redisTemplate.opsForValue().get(key))) {
            throw new CustomException(R.instance(BizCodeEnum.INVALID_CODE_ERROR.code(), BizCodeEnum.INVALID_CODE_ERROR.msg()));
        }

        // 修改邮箱设置密码
        User update = new User();
        update.setId(user.getId());
        update.setEmail(email);
        update.setPassword(passwordEncoder.encode(password));
        update.setUpdateTime(new Date());
        updateById(update);
    }

    @Override
    public void unbinding(User user, String type) {

        // 最少需要留一个绑定关系
        int i = (user.getEmail() != null ? 1 : 0) + (user.getGiteeId() != null ? 1 : 0) + (user.getGithubId() != null ? 1 : 0);
        if (i <= 1)
            throw new CustomException(R.instance(BizCodeEnum.LEAST_ONE_BINDING));

        LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(User::getId, user.getId());

        switch (type.toLowerCase()) {
            case "email" -> updateWrapper
                    .set(User::getEmail, null)
                    .set(User::getPassword, null);
            case "gitee" -> updateWrapper
                    .set(User::getGiteeId, null)
                    .set(User::getGiteeName, null);
            case "github" -> updateWrapper
                    .set(User::getGithubId, null)
                    .set(User::getGithubName, null);
            default -> throw new CustomException(R.error("解绑类型错误"));
        }

        this.update(updateWrapper);
    }

    @Override
    public boolean modifyInfo(User user, InfoVo infoVo, MultipartFile file) {

        LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();

        boolean needUpdate = false;

        if (file != null && !file.isEmpty()) {
            String[] split = file.getOriginalFilename().split("\\.");
            String suffix = split[split.length - 1];
            try {
                R<Object> r = thirdPartyFeignService.uploadAvatarByBytes(user.getId(), suffix, file.getBytes());
                if (!r.getMsg().equals(user.getAvatarUrl())) {
                    updateWrapper.set(User::getAvatarUrl, r.getMsg());
                    needUpdate = true;
                }
            } catch (Exception e) {
                log.error("modifyInfo出现错误{}", e.getMessage());
            }
        }
        if (!user.getNickname().equals(infoVo.getNickname())) {
            updateWrapper.set(User::getNickname, infoVo.getNickname());
            needUpdate = true;
        }
        if (StringUtils.hasText(infoVo.getPhone()) && !infoVo.getPhone().equals(user.getPhone())) {
            updateWrapper.set(User::getPhone, infoVo.getPhone());
            needUpdate = true;
        }

        if (needUpdate) {
            updateWrapper.eq(User::getId, user.getId().toString());
            update(updateWrapper);
        }
        return needUpdate;
    }

    @Override
    public PageVo getMemberPage(Long page, Long limit, Collection<Long> userIds) {
        Page<User> pageVo = PageDTO.of(page, limit, 1);

        if (userIds != null && !userIds.isEmpty()) {
            pageVo = this.page(pageVo,
                    new LambdaQueryWrapper<User>()
                            .in(User::getId, userIds)
                            .select(User::getId, User::getEmail, User::getNickname, User::getAvatarUrl)
            );
        }
        return new PageVo(pageVo);
    }

    @Override
    public MemberVo getMemberById(Long id) {

        User one = getOne(new LambdaQueryWrapper<User>().eq(User::getId, id)
                .select(User::getId, User::getAvatarUrl, User::getNickname, User::getEmail)
        );


        if (one != null) {
            MemberVo memberVo = new MemberVo();
            BeanUtils.copyProperties(one, memberVo);
            return memberVo;
        }

        return null;
    }


    /**
     * 检查是否频繁发送邮件
     *
     * @param key
     */
    private void checkEmailSend(String key) {
        String code = (String) redisTemplate.opsForValue().get(key);
        if (code != null) {
            Long expire = redisTemplate.getExpire(key, TimeUnit.SECONDS);
            assert expire != null;
            // 和上一次发送验证码时间不超过60s 抛出异常
            if (shiZiApiConfig.REG_TIMEOUT * 60L - expire < 60L)
                throw new CustomException(
                        R.instance(BizCodeEnum.EMAIL_SEND_TO_MORE_ERROR.code(), BizCodeEnum.EMAIL_SEND_TO_MORE_ERROR.msg()));
        }
    }

    /**
     * 异步调用发送短信服务
     *
     * @param type
     * @param to
     * @param id
     */
    private void sendCodeMail(String type, String to, Long id) {
        CompletableFuture.runAsync(() -> {
            try {
                R<Object> r = thirdPartyFeignService.sendCodeMail(type, to, id);
                if (r.getCode() != 0)
                    log.error("发送{}邮件给{}失败，异常如下：\n{}", type, to, r.getData());
            } catch (Exception e) {
                log.error("发送{}邮件给{}失败，异常如下：\n{}", type, to, e.getMessage());
            }
        }, threadPoolExecutor);
    }

}




